'use strict';
import * as child_process from 'child_process';
import { rename, stat, Stats } from 'fs';
import * as path from 'path';
import { CTagsIndex, Match, Tag } from './ctagsindex';
import { TaskQueue } from './taskqueue';
import { log } from './util';
import * as fs from 'fs';
import { convertPathForOS, getExternalIncludeFilePath } from './extension';

export class CTags {
  private baseDir: string;
  private filename: string;
  private strIncludeDirs: string;
  private index: CTagsIndex;
  private indexq: TaskQueue;
  private fileq: TaskQueue;

  constructor(baseDir: string, filename: string) {
    this.baseDir = baseDir;
    this.filename = filename;
    this.index = new CTagsIndex(this.baseDir, this.filename);
    this.indexq = new TaskQueue();
    this.fileq = new TaskQueue();
    this.strIncludeDirs = "";
  }

  public async reindex() {
    await this.fileq.append(async () => {
      await this.statAsync(path.join(this.baseDir, this.filename));
      log('found existing tags file.');
      await this.indexq.append(async () => {
        await this.index.build();
        log('indexed tags.');
      }, true);
    });
  }

  public async regenerate(args?: string[]): Promise<void> {
    log('enqueing regenerate ctags task.');
    await this.fileq.append(async () => {
      await this.regenerateFile(args);
      log('regenerated ctags.');
      await this.indexq.append(async () => {
        await this.swapTagFile();
        log('installed tags.');
        await this.index.build();
        log('indexed tags.');
      }, true);
    });
  }

  public async lookup(symbol: string): Promise<Match[] | null> {
    log(`enqueing lookup: "${symbol}".`);
    return this.indexq.append(() => {
      return this.index.lookup(symbol);
    });
  }

  public async lookupCompletions(prefix: string): Promise<Tag[] | null> {
    log(`enqueing lookup completions: "${prefix}".`);
    return this.indexq.append(() => {
      return this.index.lookupCompletions(prefix);
    });
  }

  private regenerateFile(args?: string[]): Promise<void> {
   // 获取文件所在的目录，绝对路径
    console.log("[ctags]this.baseDir:" + this.baseDir);
    const dir = path.resolve(this.baseDir) + path.sep + path.dirname(this.filename);
    console.log(`[ctags]dir: ${dir}`);
    // 判断目录是否存在，不存在则创建
    if (!fs.existsSync(dir)) {
        console.log(`[ctags]dir: ${dir} not exists, create it!`);
        try {
            fs.mkdirSync(dir);
            console.log(`[ctags]create dir: ${dir} succeeded`);
        } catch (err) {
            console.log(`[ctags]create dir: ${dir} failed`);
            return Promise.reject(new Error(`Failed to create directory: ${dir}`));
        }
    } else {
        console.log(`[ctags]dir: ${dir} exists`);
    }

    //增加外部文件定义目录 
    const externIncludeFile = getExternalIncludeFilePath();
    let externIncludeFileName = convertPathForOS(externIncludeFile.ExternalIncludeFilePath);
    console.log(`[ctags]externIncludeFileName: ${externIncludeFileName}`);

    let strExternIncludeDirs="";
    //如果extern file存在并且内容不为空，则优先使用此文件中定义的列表，否则使用includeList
    if (fs.existsSync(externIncludeFileName) && fs.readFileSync(externIncludeFileName, 'utf-8').trim() !== '') {
        console.log(`[ccall]using externIncludeFileName: ${externIncludeFileName}`);
        let externincludeList = fs.readFileSync(externIncludeFileName, 'utf-8').split('\n');
        //如果externincludeList为空，则使用includeList
        for (let i = 0; i < externincludeList.length; i++) {
          let line = externincludeList[i];
          // 去除注释和空行
          line = line.trim();
          if (line === '') {
              continue;
          }
          //如果开始以#号开始，则跳过
          if (line.startsWith('#')) {
              continue;
          }
          //添加到新表 newincludeList
          // newincludeList[seq++] = line;
          strExternIncludeDirs += line + " ";
        }

    }
    console.log(`[ctags]strExternIncludeDirs: ${strExternIncludeDirs}`);

    if(strExternIncludeDirs.trim().length > 0)
    {
      this.strIncludeDirs = strExternIncludeDirs;
    }
    else{
      this.strIncludeDirs = "."
    }
    console.log(`[ctags]strIncludeDirs: ${this.strIncludeDirs}`);


  return new Promise((resolve, reject) => {

      const command = ['ctags']
        .concat(args || [])
        .concat([`-R`])
        .concat([`-f`, this.filename + '.next', this.strIncludeDirs])
        .join(' ');
        console.log("[ctags]command:" + command);

      // 获取文件所在的目录，绝对路径
      console.log("[ctags]this.baseDir:" + this.baseDir);
      const dir = path.resolve(this.baseDir) + path.sep + path.dirname(this.filename);
      // const dir = path.dirname(this.filename);
      console.log("[ctags]dir:" + dir);
      // 判断目录是否存在，不存在则创建
      if (!fs.existsSync(dir)) {
          console.log(`[ctags]dir: ${dir} not exists, create it!`);
          fs.mkdirSync(dir);
          //创建失败则返回
          if (!fs.existsSync(dir)) {
              console.log(`[ctags]create dir: ${dir} failed`);
              return;
          }
      }
      else {
          console.log(`[ctags]dir: ${dir} exists`);
      }

      
      child_process.exec(
        command,
        { cwd: this.baseDir },
        (err, stdout, stderr) => {
          if (err) {
            log(command, err, stdout, stderr);
            reject(stderr);
          }
          resolve();
        }
      );
    });
  }

  private swapTagFile(): Promise<void> {
    return new Promise((resolve, _) => {
      //判断文件的父目录是否存在，不存在则创建
      const parentDir = path.dirname(path.join(this.baseDir, this.filename));
      if (!fs.existsSync(parentDir)) {
        fs.mkdirSync(parentDir);
        console.log(`[ctags]create [rename]parent dir: ${parentDir}`);
      }
      rename(
        path.join(this.baseDir, this.filename + '.next'),
        path.join(this.baseDir, this.filename),
        err => {
          if (err) {
            log('rename:' + err);
          }
          resolve();
        }
      );
    });
  }

  private statAsync(filename: string): Promise<Stats> {
    //判断文件的父目录是否存在，不存在则创建
    console.log(`[ctags]statAsync: ${filename}`);
    const parentDir = path.dirname(filename);
    if (!fs.existsSync(parentDir)) {
      fs.mkdirSync(parentDir);
      console.log(`[ctags]create parent dir: ${parentDir}`);
    }
    return new Promise<Stats>((resolve, reject) => {
      stat(filename, (err, stats) => {
        if (err) {
          reject(err);
        } else {
          resolve(stats);
        }
      });
    });
  }
}
